package qy.jalgotrade.bar;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author qy
 *
 */
public class BasicBar extends Bar {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6491286501390030972L;

	private final ZonedDateTime dateTime;

	private final double open;

	private final double close;

	private final double high;

	private final double low;

	private final double volume;

	private final double adjClose;

	private final Frequency frequency;

	private boolean useAdjustedValue;

	private final Map<String, Double> extra;

	/**
	 * 
	 * @param dateTime dateTime
	 * @param open open
	 * @param high high
	 * @param low low
	 * @param close close
	 * @param volume volume
	 * @param adjClose adjClose
	 * @param frequency frequency
	 * @throws Exception Exception
	 */
	public BasicBar(ZonedDateTime dateTime, double open, double high, double low, double close, double volume,
	        double adjClose, Frequency frequency) throws Exception {

		this(dateTime, open, high, low, close, volume, adjClose, frequency, new HashMap<>());
	}

	/**
	 * 
	 * @param dateTime dateTime
	 * @param open open
	 * @param high high
	 * @param low low
	 * @param close close
	 * @param volume volume
	 * @param adjClose adjClose
	 * @param frequency frequency
	 * @param extra extra
	 * @throws Exception Exception
	 */
	public BasicBar(ZonedDateTime dateTime, double open, double high, double low, double close, double volume,
	        double adjClose, Frequency frequency, Map<String, Double> extra) throws Exception {

		if (high < low) {
			throw new IllegalArgumentException(String.format("high (%s) < low (%s) on %s", high, low, dateTime));
		} else if (high < open) {
			throw new IllegalArgumentException(String.format("high (%s) < open (%s) on %s", high, open, dateTime));
		} else if (high < close) {
			throw new IllegalArgumentException(String.format("high (%s) < close (%s) on %s", high, close, dateTime));
		} else if (low > open) {
			throw new IllegalArgumentException(String.format("low (%s) > open (%s) on %s", low, open, dateTime));
		} else if (low > close) {
			throw new IllegalArgumentException(String.format("low (%s) > close (%s) on %s", low, close, dateTime));
		}

		this.dateTime = dateTime;
		this.open = open;
		this.high = high;
		this.low = low;
		this.close = close;
		this.volume = volume;
		this.adjClose = adjClose;
		this.frequency = frequency;
		this.useAdjustedValue = false;
		this.extra = extra;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getUseAdjValue()
	 */
	@Override
	public boolean getUseAdjValue() {

		return useAdjustedValue;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#setUseAdjustedValue(boolean)
	 */
	@Override
	public void setUseAdjustedValue(boolean useAdjustedValue) {

		this.useAdjustedValue = useAdjustedValue;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getDateTime()
	 */
	@Override
	public ZonedDateTime getDateTime() {

		return dateTime;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getOpen(boolean)
	 */
	@Override
	public double getOpen(boolean adjusted) {

		if (adjusted) {
			return adjClose * open / close;
		} else {
			return open;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getHigh(boolean)
	 */
	@Override
	public double getHigh(boolean adjusted) {

		if (adjusted) {
			return adjClose * high / close;
		} else {
			return high;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getLow(boolean)
	 */
	@Override
	public double getLow(boolean adjusted) {

		if (adjusted) {
			return adjClose * low / close;
		} else {
			return low;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getClose(boolean)
	 */
	@Override
	public double getClose(boolean adjusted) {

		if (adjusted) {
			return adjClose;
		} else {
			return close;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getVolume()
	 */
	@Override
	public double getVolume() {

		return volume;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getAdjClose()
	 */
	@Override
	public double getAdjClose() {

		return adjClose;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getFrequency()
	 */
	@Override
	public Frequency getFrequency() {

		return frequency;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jalgotrade.bar.Bar#getPrice()
	 */
	@Override
	public double getPrice() {

		if (useAdjustedValue) {
			return adjClose;
		} else {
			return close;
		}
	}

	@Override
	public Map<String, Double> getExtraColumns() {

		return extra;
	}

	@Override
	public boolean equals(Object obj) {

		// @formatter:off
		BasicBar rObj = (BasicBar) obj;
		return dateTime.equals(rObj.dateTime)
				&& open == rObj.open && close == rObj.close && high == rObj.high && low == rObj.low
				&& volume == rObj.volume
				&& adjClose == rObj.adjClose
				&& frequency == rObj.frequency
				&& useAdjustedValue == rObj.useAdjustedValue
				&& extra.equals(rObj.extra);
		// @formatter:on
	}

	@Override
	public String toString() {

		DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss O");
		String bts = "bar (%s - %s) {open: %s, high: %s, low: %s, close: %s, vol: %s, %s}";
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, Double> e : extra.entrySet()) {
			sb.append(e.getKey()).append(": ").append(e.getValue()).append(", ");
		}
		return String.format(bts, dateTime.format(fmt), frequency, open, high, low, close, volume,
		        sb.length() > 2 ? sb.substring(0, sb.length() - 2) : sb.toString());
	}

	public String toString(String instrument) {

		DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss O");
		String bts = "bar (%s, %s - %s) {open: %s, high: %s, low: %s, close: %s, vol: %s, %s}";
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, Double> e : extra.entrySet()) {
			sb.append(e.getKey()).append(": ").append(e.getValue()).append(", ");
		}
		return String.format(bts, instrument, dateTime.format(fmt), frequency, open, high, low, close, volume,
		        sb.length() > 2 ? sb.substring(0, sb.length() - 2) : sb.toString());
	}
}
